#include "stdlib.hpp"
#include "libfile.hpp"
#include <purewell/rwlock.hpp>

namespace purewell
{

#ifdef __PW_HAVE_POSIX_RWLOCK

typedef struct __pw_rwlock_t
{
	pthread_rwlock_t		lock;
	bool					pshared;
	int						fd;
	__pw_rwlock_t() : pshared(false), fd(-1) {}
} __pw_rwlock_t;

#	define _xchg() &(((__pw_rwlock_t*)(m_pData))->lock)
#	define _xchgattr() ((pthread_rwlockattr_t*)(m_pData))

#endif

#ifdef __PW_HAVE_POSIX_RWLOCK

inline
static
void
__PWDestroyRWLock(__pw_rwlock_t* dat)
{
	pthread_rwlock_destroy(&(dat->lock));
	if ( dat->pshared )
	{
		__pw_shinst_t si;
		si.p = dat;
		si.fd = dat->fd;
		si.size = sizeof(__pw_rwlock_t);
		__PWDestroySharedInstance(si);
	}
	else
	{
		delete dat;
	}
}

inline
static
__pw_rwlock_t*
__PWCreateRWLockShared(const pthread_rwlockattr_t* attr)
{
	__pw_rwlock_t* dat(NULL);
	__pw_shinst_t si;

	do
	{
		if ( !__PWCreateSharedInstance(si, sizeof(__pw_rwlock_t)) )
		{
			break;
		}

		dat = (__pw_rwlock_t*)si.p;

		if ( 0 != pthread_rwlock_init(&(dat->lock), attr) )
		{
			dat = NULL;
			break;
		}

		dat->pshared = true;
		dat->fd = si.fd;

		return dat;
	} while (false);

	if ( si.p )
	{
		__PWDestroySharedInstance(si);
	}

	return NULL;
}

inline
static
__pw_rwlock_t*
__PWCreateRWLockPrivate(const pthread_rwlockattr_t* attr)
{
	__pw_rwlock_t* dat(NULL);

	do
	{
		dat = new __pw_rwlock_t;
		if ( NULL == dat ) break;

		if ( 0 != pthread_rwlock_init(&(dat->lock), attr) )
		{
			break;
		}

		return dat;
	} while (false);

	if ( dat )
	{
		delete dat;
		dat = NULL;
	}

	return NULL;
}

#endif//__PW_HAVE_POSIX_RWLOCK

PWRWLock*
PWRWLock::create(bool pshared)
{
#ifdef __PW_HAVE_POSIX_RWLOCK
	PWRWLockAttribute* pattr(PWRWLockAttribute::create());
	if ( NULL == pattr ) return NULL;

	PWRWLock* cls(NULL);

	do
	{
		if ( !pattr->setShared(pshared) ) break;

		cls = create(pattr);

	} while (false);

	PWRWLockAttribute::release(pattr);

	return cls;
#else
#	ifdef WIN32
	if ( pshared )
	{
		__PWERRNO_NOIMPL();
		return NULL;
	}

	return create((PWRWLock*)NULL);
#	else
	__PWERRNO_NOIMPL();
	return NULL;
#	endif//WIN32
#endif//__PW_HAVE_POSIX_RWLOCK
}

PWRWLock*
PWRWLock::create(const PWRWLockAttribute* pattr)
{
#if defined(__PW_HAVE_POSIX_RWLOCK)
	bool pshared(false);
	const pthread_rwlockattr_t* nattr(NULL);
	if ( pattr )
	{
		if ( !pattr->getShared(pshared) ) return NULL;
		nattr = (const pthread_rwlockattr_t*)(pattr->getData());
	}

	PWRWLock* cls(new PWRWLock);
	if ( NULL == cls ) return NULL;

	__pw_rwlock_t* dat(NULL);

	do
	{
		dat = pshared ? __PWCreateRWLockShared(nattr)
			: __PWCreateRWLockPrivate(nattr);
		if ( NULL == dat ) break;

		cls->m_pData = dat;
		return cls;
	} while (false);

	if ( cls ) delete cls;

	return NULL;
#else
#	ifdef WIN32
	PWRWLock* cls(new PWRWLock);
	if ( NULL == cls ) return NULL;

	SRWLOCK* dat(NULL);

	do
	{
		if ( NULL == (dat = new SRWLOCK) ) break;
		InitializeSRWLock(dat);

		cls->m_pData = dat;
		return cls;
	} while (false);

	if ( cls ) delete cls;

	return NULL;
#	else
	__PWERRNO_NOIMPL();
	return NULL;
#	endif
#endif
}

void
PWRWLock::release(PWRWLock* lock)
{
	delete lock;
}


PWRWLock::PWRWLock()
{
}

PWRWLock::~PWRWLock()
{
#ifdef __PW_HAVE_POSIX_RWLOCK
	if ( NULL == m_pData ) return;
	__PWDestroyRWLock((__pw_rwlock_t*)m_pData);
	m_pData = NULL;
#else
#	ifdef WIN32
	if ( NULL == m_pData ) return;
	SRWLOCK* dat((SRWLOCK*)m_pData);
	delete dat;
	m_pData = NULL;
#	else
	// do nothing...
#	endif//WIN32
#endif//__PW_HAVE_POSIX_RWLOCK
}

void
PWRWLock::lock(int type)
{
#if defined(__PW_HAVE_POSIX_RWLOCK)
	if ( PWIO_READ == type )
	{
		pthread_rwlock_rdlock(_xchg());
	}
	else
	{
		pthread_rwlock_wrlock(_xchg());
	}
#else
#	ifdef WIN32
	if ( PWIO_READ == type )
	{
		AcquireSRWLockShared((PSRWLOCK)m_pData);
	}
	else
	{
		AcquireSRWLockExclusive((PSRWLOCK)m_pData);
	}
#	else
	__PWERRNO_NOIMPL();
#	endif//WIN32
#endif
}

bool
PWRWLock::lockTest(int type)
{
#if defined(__PW_HAVE_POSIX_RWLOCK)
	if ( PWIO_READ == type )
	{
		return 0 == pthread_rwlock_tryrdlock(_xchg());
	}
	return 0 == pthread_rwlock_trywrlock(_xchg());
#else
	__PWERRNO_NOIMPL();
	return false;
#endif
}

bool
PWRWLock::lockTimeLimit(int type, size_t msec)
{
#if defined(__PW_HAVE_POSIX_RWLOCK)
	struct timespec ts;
	ts.tv_sec = msec / 1000;
	ts.tv_nsec = (msec % 1000) * 1000000LL;

	if ( PWIO_READ == type )
	{
		return 0 == pthread_rwlock_timedrdlock(_xchg(), &ts);
	}

	return 0 == pthread_rwlock_timedwrlock(_xchg(), &ts);
#else
	__PWERRNO_NOIMPL();
	return false;
#endif
}

void
PWRWLock::unlock(void)
{
#if defined(__PW_HAVE_POSIX_RWLOCK)
	pthread_rwlock_unlock(_xchg());
#else
#	ifdef WIN32
	ReleaseSRWLockShared((PSRWLOCK)m_pData);
	ReleaseSRWLockExclusive((PSRWLOCK)m_pData);
#	else
	__PWERRNO_NOIMPL();
#	endif//WIN32
#endif
}

PWRWLockAttribute::PWRWLockAttribute()
{
#if defined(__PW_HAVE_POSIX_RWLOCK)
	pthread_rwlockattr_t* pthis(new pthread_rwlockattr_t);
	if ( NULL == pthis )
	{
		return;
	}

	if ( 0 != pthread_rwlockattr_init(pthis) )
	{
		delete pthis;
		return;
	}

	m_pData = pthis;
#else
	__PWERRNO_NOIMPL();
	m_pData = NULL;
#endif
}

PWRWLockAttribute::~PWRWLockAttribute()
{
#if defined(__PW_HAVE_POSIX_RWLOCK)
	if ( NULL != m_pData )
	{
		pthread_rwlockattr_t* pthis((pthread_rwlockattr_t*)m_pData);
		delete pthis;
		m_pData = NULL;
	}
#else
	__PWERRNO_NOIMPL();
#endif
}

PWRWLockAttribute*
PWRWLockAttribute::create(void)
{
#if defined(__PW_HAVE_POSIX_RWLOCK)
	PWRWLockAttribute* pthis(new PWRWLockAttribute);
	if ( NULL == pthis )
	{
		return NULL;
	}

	if ( NULL == pthis->m_pData )
	{
		delete pthis;
		return NULL;
	}

	return pthis;
#else
	__PWERRNO_NOIMPL();
	return NULL;
#endif
}

void
PWRWLockAttribute::release(PWRWLockAttribute* obj)
{
	delete obj;
}

bool
PWRWLockAttribute::setShared(bool pshared)
{
#ifdef __PW_HAVE_POSIX_RWLOCK
	pthread_rwlockattr_t* pthis((pthread_rwlockattr_t*)m_pData);
	return 0 == pthread_rwlockattr_setpshared(pthis,
		pshared?PTHREAD_PROCESS_SHARED:PTHREAD_PROCESS_PRIVATE);
#else
	__PWERRNO_NOIMPL();
	return false;
#endif
}

bool
PWRWLockAttribute::getShared(bool& pshared) const
{
#ifdef __PW_HAVE_POSIX_RWLOCK
	const pthread_rwlockattr_t* pthis((const pthread_rwlockattr_t*)m_pData);

	int type;
	bool res( 0 == pthread_rwlockattr_getpshared(pthis, &type) );

	if ( res )
	{
		pshared = (type == PTHREAD_PROCESS_SHARED);
	}

	return res;
#else
	__PWERRNO_NOIMPL();
	return false;
#endif
}

};//namespace purewell

